/*
 * mbport_network.c
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-12-31     nx      	   the first version
 *
 */

#include "mbport_network.h"
#include "modbus_utils.h"

#if (MODBUS_ETH_ENABLED > 0)


static mb_uint8_t network_inited;
static mbport_network_t mb_network_obj;

static int _nework_cfg(mbport_netcfg_t *netcfg)
{
    WSADATA wsaData;

    if (network_inited == 0)
    {
        if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
            return -1;

        network_inited = 1;
    }
    return 0;
}

static int _tcp_send(int sock, const char *buf, int len, int flags, const SOCKADDR *to, int tolen)
{
    return send(sock, buf, len, flags);
}

static int _tcp_recv(int sock, char *buf, int len, int flags, SOCKADDR *from, int *fromlen)
{
    return recv(sock, buf, len, flags);
}

static int _udp_send(int sock, const char *buf, int len, int flags, const SOCKADDR *to, int tolen)
{
    return sendto(sock, buf, len, flags, to, tolen);
}

static int _udp_recv(int sock, char *buf, int len, int flags, SOCKADDR *from, int *fromlen)
{
    return recvfrom(sock, buf, len, flags, from, fromlen);
}

static mb_err_t netif_connect(mbport_network_t *mb_network)
{
    int ret;
    unsigned long mode = 1;

    if (mb_network->proto == MBPORT_NET_TCP)
    {
        /* Client */
        if (mb_network->side == MBPORT_NET_CLIENT)
        {
            if (mb_network->con_sock == -1)
            {
                ret = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
                if (ret < 0)
                {
                    goto err_exit;
                }
                mb_network->con_sock = ret;
            }

            /* Set the socket in non-blocking */
            ret = ioctlsocket(mb_network->con_sock, FIONBIO, &mode);
            if (ret < 0)
            {
                MODBUS_DBG("ioctlsocket failed with error: %d\n", ret);
                closesocket(mb_network->con_sock);
                mb_network->con_sock = -1;
                goto err_exit;
            }

            connect(mb_network->con_sock, (struct sockaddr *)&mb_network->remote_addr, sizeof(struct sockaddr));

            /* Restart the socket mode */
            mode = 0;
            ret = ioctlsocket(mb_network->con_sock, FIONBIO, &mode);
            if (ret < 0)
            {
                MODBUS_DBG("ioctlsocket failed with error: %d\n", ret);
                closesocket(mb_network->con_sock);
                mb_network->con_sock = -1;
                goto err_exit;
            }

            /* Check if the socket is ready */
            FD_ZERO(&mb_network->allset);
            FD_SET(mb_network->con_sock, &mb_network->allset);
            mb_network->tval.tv_sec = mb_network->conn_tmo / 1000;
            mb_network->tval.tv_usec = (mb_network->conn_tmo % 1000) * 1000;
            ret = select(mb_network->con_sock + 1, NULL, &mb_network->allset, NULL, &mb_network->tval);
            if (ret <= 0)
            {
                closesocket(mb_network->con_sock);
                mb_network->con_sock = -1;
                goto err_exit;
            }
        }
        /* Server */
        else if (mb_network->side == MBPORT_NET_SERVER)
        {
            if (mb_network->svr_sock == -1)
            {
                ret = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
                if (ret < 0)
                {
                    MODBUS_DBG("Create socket failed.\r\n");
                    goto err_exit;
                }
                mb_network->svr_sock = ret;

                ret = bind(mb_network->svr_sock,
                    (struct sockaddr *)&mb_network->local_addr, sizeof(struct sockaddr));
                if (ret < 0)
                {
                    MODBUS_DBG("Bind socket failed.\r\n");
                    closesocket(mb_network->svr_sock);
                    goto err_exit;
                }
                else if (listen(mb_network->svr_sock, 2) < 0)
                {
                    MODBUS_DBG("Listen socket failed.\r\n");
                    closesocket(mb_network->svr_sock);
                    goto err_exit;
                }
            }

            FD_ZERO(&mb_network->allset);
            FD_SET(mb_network->svr_sock, &mb_network->allset);

            mb_network->tval.tv_sec = mb_network->conn_tmo / 1000;
            mb_network->tval.tv_usec = (mb_network->conn_tmo % 1000) * 1000;

            /* Accept to client */
            ret = select(mb_network->svr_sock + 1, &mb_network->allset, NULL, NULL, &mb_network->tval);
            if (ret <= 0)
            {
                goto err_exit;
            }

            else if (FD_ISSET(mb_network->svr_sock, &mb_network->allset))
            {
                ret = accept(mb_network->svr_sock, NULL, NULL);
                if (ret < 0)
                {
                    goto err_exit;
                }
                else
                {
                    mb_network->con_sock = ret;
                }
            }
        }

        mb_network->send = _tcp_send;
        mb_network->recv = _tcp_recv;
        return MB_ENOERR;
    }
    else if (mb_network->proto == MBPORT_NET_UDP)
    {
        if (mb_network->con_sock == -1)
        {
            ret = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            if (ret < 0)
            {
                goto err_exit;
            }
            mb_network->svr_sock = -1;
            mb_network->con_sock = ret;
        }

        /* Server */
        if (mb_network->side == MBPORT_NET_SERVER)
        {
            ret = bind(mb_network->con_sock, (struct sockaddr *)&mb_network->local_addr, sizeof(struct sockaddr));
            if (ret < 0)
            {
                MODBUS_DBG("Bind socket failed.\r\n");
                closesocket(mb_network->svr_sock);
                goto err_exit;
            }
        }

        mb_network->send = _udp_send;
        mb_network->recv = _udp_recv;
        return MB_ENOERR;
    }

err_exit:
    return -MB_EIO;
}

static mb_err_t _mbport_network_open(modbus_port_t *mb_port)
{
    return netif_connect((mbport_network_t *)mb_port);
}

static mb_int32_t _mbport_network_close(modbus_port_t *mb_port)
{
    mbport_network_t *mb_network = (mbport_network_t *)mb_port;

    /* Close all client sockets. */
    if (mb_network->con_sock >= 0)
    {
        closesocket(mb_network->con_sock);
        mb_network->con_sock = -1;
    }

    /* Close the listener socket. */
    if (mb_network->svr_sock >= 0)
    {
        closesocket(mb_network->svr_sock);
        mb_network->svr_sock = -1;
    }
    return MB_ENOERR;
}

static mb_int32_t _mbport_network_send(modbus_port_t *mb_port,
    mb_uint8_t *frame, mb_uint16_t length, mb_uint32_t timeout)
{
    int ret;
    mb_uint16_t bytes_sent = 0;
    mbport_network_t *mb_network = (mbport_network_t *)mb_port;

    if ((mb_network->con_sock == -1) && (netif_connect(mb_network) != MB_ENOERR))
    {
        return 0;
    }

    do
    {
        ret = mb_network->send(mb_network->con_sock, (char *)&frame[bytes_sent], length - bytes_sent, 0,
            (struct sockaddr *)&mb_network->remote_addr, sizeof(struct sockaddr));
        if (ret > 0)
        {
            bytes_sent += ret;
        }
        else if (ret == 0)
        {
            continue;
        }
        else if (ret < 0)
        {
            closesocket(mb_network->con_sock);
            mb_network->con_sock = -1;
            break;
        }
    } while (bytes_sent != length);

    return bytes_sent;
}


static mb_int32_t _mbport_network_recv(modbus_port_t *mb_port,
    mb_uint8_t *frame, mb_uint16_t length, mb_uint32_t timeout)
{
    int ret;
    int fromlen;
    fd_set read_fds;
    fd_set exception_fds;
    mb_uint8_t *data_ptr;
    mbport_network_t *mb_network = (mbport_network_t *)mb_port;

    if ((mb_network->con_sock == -1) && (netif_connect(mb_network) != MB_ENOERR))
    {
        return 0;
    }

    data_ptr = frame;
    FD_ZERO(&read_fds);
    FD_ZERO(&exception_fds);
    FD_SET(mb_network->con_sock, &read_fds);
    FD_SET(mb_network->con_sock, &exception_fds);

    while (length > 0)
    {
        if (mb_network->count > 0)
        {
            *data_ptr++ = mb_network->buf[mb_network->rd_pos++];
            mb_network->count -= 1;
            length -= 1;
        }
        else
        {
            mb_network->tval.tv_sec = timeout / 1000 * 5;
            mb_network->tval.tv_usec = (timeout % 1000) * 1000;
            ret = select(mb_network->con_sock + 1, &read_fds, NULL, &exception_fds, &mb_network->tval);
            if (ret > 0)
            {
                if (FD_ISSET(mb_network->con_sock, &read_fds))
                {
                    fromlen = sizeof(struct sockaddr);
                    ret = mb_network->recv(mb_network->con_sock, (char *)mb_network->buf, MOPORT_NET_ADU_SIZE_MAX, 0,
                        (struct sockaddr *)&mb_network->remote_addr, &fromlen);
                    if (ret < 0)
                    {
                        closesocket(mb_network->con_sock);
                        mb_network->con_sock = -1;
                        goto err_exit;
                    }
                    else if (ret == 0)
                    {
                        continue;
                    }
                    mb_network->count += ret;
                    mb_network->rd_pos = 0;
                    timeout = 10;
                }
                else if (FD_ISSET(mb_network->con_sock, &exception_fds))
                {
                    closesocket(mb_network->con_sock);
                    mb_network->con_sock = -1;
                    goto err_exit;
                }
            }
            else
            {
                goto err_exit;
            }
        }
    }

err_exit:
    return (data_ptr - frame);
}

static mb_int32_t _mbport_network_control(modbus_port_t *mb_port, mb_uint32_t cmd, void *args)
{
    mbport_netcfg_t *mb_netcfg = (mbport_netcfg_t *)args;
    mbport_network_t *mb_network = (mbport_network_t *)mb_port;

    if (cmd == MODBUS_CTRL_WRITE)
    {
        if (_nework_cfg(mb_netcfg) != 0)
            return -MB_EIO;

        memset(&mb_network->local_addr, 0, sizeof(struct sockaddr));
        if ((mb_netcfg->local_ip != MB_NULL) && (mb_netcfg->local_port != 0))
        {
            mb_network->local_addr.sin_family = AF_INET;
            mb_network->local_addr.sin_port = htons(mb_netcfg->local_port == 0 ?
                MBPORT_NET_DEFAULT_PORT : mb_netcfg->local_port);
            mb_network->local_addr.sin_addr.s_addr = (u_long)inet_addr((const char *)mb_netcfg->local_ip);
        }

        memset(&mb_network->remote_addr, 0, sizeof(struct sockaddr));
        if ((mb_netcfg->remote_ip != MB_NULL) && (mb_netcfg->remote_port != 0))
        {
            mb_network->remote_addr.sin_family = AF_INET;
            mb_network->remote_addr.sin_port = htons(mb_netcfg->remote_port == 0 ?
                MBPORT_NET_DEFAULT_PORT : mb_netcfg->remote_port);
            mb_network->remote_addr.sin_addr.s_addr = (u_long)inet_addr((const char *)mb_netcfg->remote_ip);
        }
        mb_network->conn_tmo = mb_netcfg->connect_tmo;
    }
    return MB_ENOERR;
}

mbport_network_t *mbport_network_create(mbport_net_side_t side, mbport_net_proto_t proto)
{
    mbport_network_t *mb_network = &mb_network_obj;
    mbport_netcfg_t mb_netcfg = NETWORK_DEFAULT_CFG;

    _mbport_network_control((modbus_port_t *)mb_network, MODBUS_CTRL_WRITE, &mb_netcfg);

    mb_network->side = side;
    mb_network->proto = proto;
    mb_network->svr_sock = -1;
    mb_network->con_sock = -1;
    mb_network->send = MB_NULL;
    mb_network->recv = MB_NULL;

    mb_network->parent.open_fn = _mbport_network_open;
    mb_network->parent.close_fn = _mbport_network_close;
    mb_network->parent.send_fn = _mbport_network_send;
    mb_network->parent.recv_fn = _mbport_network_recv;
    mb_network->parent.ctrl_fn = _mbport_network_control;

    return mb_network;
}

#endif
